package com.feiyuan.generator.service.impl;

import com.feiyuan.common.exception.BusinessException;
import com.feiyuan.generator.mapper.DataSourceMapper;
import com.feiyuan.generator.pojo.dto.GeneratorCodeDTO;
import com.feiyuan.generator.pojo.entity.DataSourceEntity;
import com.feiyuan.generator.service.TableListService;
import com.feiyuan.generator.utils.ZipUtils;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.*;
import java.util.*;

/**
 * @ClassName TableListServiceImpl
 * @Description TODO
 * @Author Xupf xupengfei131276@126.com
 * @Date 2021/9/30 9:45
 * @Version 1.0
 */
@Service
@Slf4j
public class TableListServiceImpl implements TableListService {

    @Resource
    private DataSourceMapper dataSourceMapper;

    @Override
    public Object listTables(String tableName) {
        try {
            // 1.获取数据库连接
            Connection conn = this.getConn();
            if (conn == null) {
                throw new BusinessException("请去添加数据源信息", -101);
            }
            // 2.获取数据库中的表信息
            DatabaseMetaData dbMetaData = conn.getMetaData();
            /**
             * 第一个是数据库名称，对于MySQL，则对应相应的数据库，对于Oracle来说，则是对应相应的数据库实例，可以不填，也可以直接使用Connection的实例对象中的getCatalog()方法返回的值填充；
             *
             * 第二个是模式，可以理解为数据库的登录名，而对于Oracle也可以理解成对该数据库操作的所有者的登录名。对于Oracle要特别注意，其登陆名必须是大写，不然的话是无法获取到相应的数据，而MySQL则不做强制要求。
             *
             * 第三个是表名称，一般情况下如果要获取所有的表的话，可以直接设置为null，如果设置为特定的表名称，则返回该表的具体信息。
             *
             * 第四个是类型标准,以数组形式传值，有"TABLE"、"VIEW"、"SYSTEM TABLE"、"GLOBAL TEMPORARY"、"LOCAL TEMPORARY"、"ALIAS" 和 "SYNONYM"这几个经典的类型，一般使用”TABLE”，即获取所有类型为TABLE的表
             */
            ResultSet rs = dbMetaData.getTables(conn.getCatalog(), null, null, new String[] { "TABLE" });
            int i = 0;
            List<Map<String, String>> list = new ArrayList<>();
            while (rs.next()) {
                Map<String, String> map = new HashMap<>();
                String tableName1 = rs.getString("TABLE_NAME");
                String tableType = rs.getString("TABLE_TYPE");
                String tableCat = rs.getString("TABLE_CAT");
                String remarks = rs.getString("REMARKS");
                log.info("表名：{}", tableName1);
                log.info("表类型：{}", tableType);
                log.info("表所属数据库：{}", tableCat);
                log.info("表备注：{}", remarks);
                if (!StringUtils.isEmpty(tableName) && tableName.equals(tableName1)) {
                    map.put("id", (i + 1) + "");
                    map.put("tableName", tableName1);
                    map.put("tableType", tableType);
                    map.put("tableCat", tableCat);
                    map.put("remarks", remarks);
                    list.add(map);
                    break;
                } else {
                    map.put("id", (i + 1) + "");
                    map.put("tableName", tableName1);
                    map.put("tableType", tableType);
                    map.put("tableCat", tableCat);
                    map.put("remarks", remarks);
                    list.add(map);
                }
                i ++;
            }
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取数据库连接
     * @return
     */
    private Connection getConn() {
        // 1.查询数据源中默认的一条数据源
        DataSourceEntity dataSource = dataSourceMapper.selectOne(new QueryWrapper<DataSourceEntity>().eq("is_default", true));
        Connection conn = null;
        if (dataSource != null) {
            try {
                Class.forName(dataSource.getClassName());
                conn = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUserName(), dataSource.getPassword());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    @Override
    public byte[] generatorCode(GeneratorCodeDTO generatorCode) throws IOException {
        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = "";
        if (generatorCode.getOutPutDirType().intValue() == 0) {
            projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath + "/src/main/java");
        } else if (generatorCode.getOutPutDirType().intValue() == 1) {
            gc.setOutputDir(generatorCode.getOutPutDir() + "/src/main/java");
        } else {
            projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath + "/generator/src/main/java");
        }
        gc.setAuthor(generatorCode.getAuthor());
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setIdType(IdType.AUTO); //主键策略
        gc.setSwagger2(true);//开启Swagger2模式
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceEntity dataSource = dataSourceMapper.selectOne(new QueryWrapper<DataSourceEntity>().eq("is_default", true));
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl(dataSource.getUrl());
        dsc.setDriverName(dataSource.getClassName());
        dsc.setUsername(dataSource.getUserName());
        dsc.setPassword(dataSource.getPassword());
        Integer dataType = dataSource.getDataType();
        if (dataType.intValue() == 0) {
            dsc.setDbType(DbType.MYSQL);
        } else {
            dsc.setDbType(DbType.ORACLE);
        }
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(generatorCode.getPackageParent());
        pc.setEntity(generatorCode.getPackageEntity()); //此对象与数据库表结构一一对应，通过 DAO 层向上传输数据源对象。
        pc.setModuleName(generatorCode.getModuleName());
        mpg.setPackageInfo(pc);

        InjectionConfig injectionConfig = new InjectionConfig() {
            //自定义属性注入:abc
            //在.ftl(或者是.vm)模板中，通过${cfg.abc}获取属性
            @Override
            public void initMap() {
//                Map<String, Object> map = new HashMap<>();
//                map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");
//                this.setMap(map);
            }
        };
        String vueOutFilePath = projectPath + "/generator/src/main/resources" + "/";
        List<FileOutConfig> files = new ArrayList<FileOutConfig>();
        files.add(new FileOutConfig("templates/demo.vue.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                String outFile = vueOutFilePath + tableInfo.getEntityName() + ".vue";
                return outFile;
            }
        });
        files.add(new FileOutConfig("templates/demo-save-or-update.vue.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                String outFile = vueOutFilePath + tableInfo.getEntityName() + "-save-or-update.vue";
                return outFile;
            }
        });
        files.add(new FileOutConfig("templates/demo.js.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                String outFile = vueOutFilePath + tableInfo.getEntityName() + ".js";
                return outFile;
            }
        });
        injectionConfig.setFileOutConfigList(files);
        mpg.setCfg(injectionConfig);

        // 模板配置
        TemplateConfig template = new TemplateConfig();
        template.setController("/templates/controller.java");
//        template.setEntity("/templates/Entity.java");
//        template.setMapper("/templates/Mapper.java");
//        template.setXml("/templates/Mapper.xml");
        template.setService("/templates/Service.java");
        template.setServiceImpl("/templates/ServiceImpl.java");
        mpg.setTemplate(template);
//        mpg.setTemplateEngine(new MyTemplateEngine());

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok
        strategy.setLogicDeleteFieldName("is_deleted");//逻辑删除字段名
        strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀（确保tinyint(1)）
        strategy.setRestControllerStyle(true); //restful api风格控制器

        TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
        TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
        List<TableFill> fillList = new ArrayList<>();
        fillList.add(createTime);
        fillList.add(updateTime);
        strategy.setTableFillList(fillList);

//        strategy.setInclude(scanner("表名，多个英文逗号分割").split(","));
        // TODO 下次记得改这里
        List<String> tableList = generatorCode.getTableList();
        String[] objects = tableList.toArray(new String[tableList.size()]);
        strategy.setInclude(objects);
        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();

        // 7.压缩
//        String projectPath = System.getProperty("user.dir");
        byte[] bytes = ZipUtils.toZip(projectPath + "/generator", new ByteArrayOutputStream(), true);

        // 8.删除源文件
//        this.deleteFiles(projectPath + "/generator");

        return bytes;
    }

    @Override
    public Map<String, Object> generatorCode1(GeneratorCodeDTO generatorCode) throws IOException {
        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = "";
        if (generatorCode.getOutPutDirType().intValue() == 0) {
            projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath + "/src/main/java");
        } else if (generatorCode.getOutPutDirType().intValue() == 1) {
            gc.setOutputDir(generatorCode.getOutPutDir() + "/src/main/java");
        } else {
            projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath + "/generator/src/main/java");
        }
        gc.setAuthor(generatorCode.getAuthor());
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setServiceName("%sService");	//去掉Service接口的首字母I
        gc.setIdType(IdType.AUTO); //主键策略
        gc.setSwagger2(true);//开启Swagger2模式
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceEntity dataSource = dataSourceMapper.selectOne(new QueryWrapper<DataSourceEntity>().eq("is_default", true));
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl(dataSource.getUrl());
        dsc.setDriverName(dataSource.getClassName());
        dsc.setUsername(dataSource.getUserName());
        dsc.setPassword(dataSource.getPassword());
        Integer dataType = dataSource.getDataType();
        if (dataType.intValue() == 0) {
            dsc.setDbType(DbType.MYSQL);
        } else {
            dsc.setDbType(DbType.ORACLE);
        }
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(generatorCode.getPackageParent());
        pc.setEntity(generatorCode.getPackageEntity()); //此对象与数据库表结构一一对应，通过 DAO 层向上传输数据源对象。
        pc.setModuleName(generatorCode.getModuleName());
        mpg.setPackageInfo(pc);

        InjectionConfig injectionConfig = new InjectionConfig() {
            //自定义属性注入:abc
            //在.ftl(或者是.vm)模板中，通过${cfg.abc}获取属性
            @Override
            public void initMap() {
//                Map<String, Object> map = new HashMap<>();
//                map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");
//                this.setMap(map);
            }
        };
        String vueOutFilePath = projectPath + "/generator/src/main/resources" + "/";
        List<FileOutConfig> files = new ArrayList<FileOutConfig>();
        files.add(new FileOutConfig("templates/demo.vue.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                String outFile = vueOutFilePath + tableInfo.getEntityName() + ".vue";
                return outFile;
            }
        });
        files.add(new FileOutConfig("templates/demo-save-or-update.vue.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                String outFile = vueOutFilePath + tableInfo.getEntityName() + "-save-or-update.vue";
                return outFile;
            }
        });
        files.add(new FileOutConfig("templates/demo.js.vm") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                String outFile = vueOutFilePath + tableInfo.getEntityName() + ".js";
                return outFile;
            }
        });
        injectionConfig.setFileOutConfigList(files);
        mpg.setCfg(injectionConfig);

        // 模板配置
        TemplateConfig template = new TemplateConfig();
        template.setController("/templates/controller.java");
//        template.setEntity("/templates/Entity.java");
//        template.setMapper("/templates/Mapper.java");
//        template.setXml("/templates/Mapper.xml");
        template.setService("/templates/Service.java");
        template.setServiceImpl("/templates/ServiceImpl.java");
        mpg.setTemplate(template);
//        mpg.setTemplateEngine(new MyTemplateEngine());

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok
        strategy.setLogicDeleteFieldName("is_deleted");//逻辑删除字段名
        strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀（确保tinyint(1)）
        strategy.setRestControllerStyle(true); //restful api风格控制器

        TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
        TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
        List<TableFill> fillList = new ArrayList<>();
        fillList.add(createTime);
        fillList.add(updateTime);
        strategy.setTableFillList(fillList);

//        strategy.setInclude(scanner("表名，多个英文逗号分割").split(","));
        List<String> tableList = generatorCode.getTableList();
        String[] objects = tableList.toArray(new String[tableList.size()]);
        strategy.setInclude(objects);
        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();

        // 7、返回生成文件的地址
        String path = projectPath + "/generator";
        Map<String, Object> map = new HashMap<>();
        map.put("path", path);
        map.put("code", 200);

        return map;
    }

    /**
     * 删除源文件
     * @param pathUrl
     * @throws IOException
     */
    private void deleteFiles(String pathUrl) throws IOException {
        Path path = Paths.get(pathUrl);

        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                System.out.println("文件被删除:" + file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                System.out.println("文件夹被删除：" + dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}
